﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.ComponentModel;
using System.Xml.Serialization;
using ICeFramework.Serialization;

namespace ICeFramework.Win.UI.FSWatcher
{
    public delegate void FSWatcherHistoryItemHandler(object sender, FSWatcherHistoryItemEventArgs e);

    public class FSWatcher : Component
    {
        private FileSystemWatcher _fsWatcher;
        private FSWatcherItemCollection _items;

        #region events
        public event RenamedEventHandler Renamed;
        public event ErrorEventHandler Error;
        public event FileSystemEventHandler Deleted;
        public event FileSystemEventHandler Created;
        public event FileSystemEventHandler Changed;
        public event FSWatcherHistoryItemHandler ItemHistory;
        #endregion

        private string _filter;
        #region public string Filter
        /// <summary>
        /// Get/Sets the Filter of the FSWatcher
        /// </summary>
        /// <value></value>
        public string Filter
        {
            get { return _filter; }
            set
            {
                _filter = value;
                if (!DesignMode)
                {
                    _fsWatcher.Filter = value;
                }
            }
        }
        #endregion

        private bool _includeSubdirectories;
        #region public bool IncludeSubdirectories
        /// <summary>
        /// Get/Sets the IncludeSubdirectories of the FSWatcher
        /// </summary>
        /// <value></value>
        public bool IncludeSubdirectories
        {
            get { return _includeSubdirectories; }
            set
            {
                _includeSubdirectories = value;
                if (!DesignMode)
                {
                    _fsWatcher.IncludeSubdirectories = value;
                }
            }
        }
        #endregion

        private int _internalBufferSize;
        #region public int InternalBufferSize
        /// <summary>
        /// Get/Sets the InternalBufferSize of the FSWatcher
        /// </summary>
        /// <value></value>
        public int InternalBufferSize
        {
            get { return _internalBufferSize; }
            set
            {
                _internalBufferSize = value;
                if (!DesignMode)
                {
                    _fsWatcher.InternalBufferSize = value;
                }
            }
        }
        #endregion

        private NotifyFilters _notifyFilter;
        #region public NotifyFilters NotifyFilter
        /// <summary>
        /// Get/Sets the NotifyFilter of the FSWatcher
        /// </summary>
        /// <value></value>
        public NotifyFilters NotifyFilter
        {
            get { return _notifyFilter; }
            set
            {
                _notifyFilter = value;
                if (!DesignMode)
                {
                    _fsWatcher.NotifyFilter = value;
                }
            }
        }
        #endregion

        private string _xmlPersistencePath;
        #region public string XmlPersistencePath
        /// <summary>
        /// Get/Sets the XmlPersistencePath of the FSWatcher
        /// </summary>
        /// <value></value>
        public string XmlPersistencePath
        {
            get { return _xmlPersistencePath; }
            set { _xmlPersistencePath = value; }
        }
        #endregion

        private string _path;
        #region public string Path
        /// <summary>
        /// Get/Sets the Path of the FSWatcher
        /// </summary>
        /// <value></value>
        public string Path
        {
            get { return _path; }
            set
            {
                _path = value;
                if (!DesignMode)
                {
                    _fsWatcher.Path = value;
                }
            }
        }
        #endregion

        #region public FSWatcher()
        /// <summary>
        /// Initializes a new instance of the <b>FSWatcher</b> class.
        /// </summary>
        public FSWatcher()
        {
            _fsWatcher = new FileSystemWatcher();
        }
        #endregion

        public void Start()
        {
            InitializeComponent();
            _fsWatcher.EnableRaisingEvents = true;
        }

        public void Stop()
        {
            _fsWatcher.EnableRaisingEvents = false;
        }

        private void InitializeComponent()
        {
            _items = new FSWatcherItemCollection();
            if (!LoadFromXml())
            {
                _fsWatcher.Path = string.IsNullOrEmpty(_path) ? "c:\\" : _path;
                _items.RootDir = _fsWatcher.Path;
            }
            _fsWatcher.Changed += new FileSystemEventHandler(_fsWatcher_Changed);
            _fsWatcher.Created += new FileSystemEventHandler(_fsWatcher_Created);
            _fsWatcher.Deleted += new FileSystemEventHandler(_fsWatcher_Deleted);
            _fsWatcher.Error += new ErrorEventHandler(_fsWatcher_Error);
            _fsWatcher.Renamed += new RenamedEventHandler(_fsWatcher_Renamed);
            _fsWatcher.Filter = _filter;
            _fsWatcher.IncludeSubdirectories = _includeSubdirectories;
            _fsWatcher.InternalBufferSize = _internalBufferSize;
            _fsWatcher.NotifyFilter = _notifyFilter;
            _xmlPersistencePath = string.IsNullOrEmpty(_xmlPersistencePath) ? "d:\\" : _xmlPersistencePath;
        }

        #region void _fsWatcher_Renamed(object sender, RenamedEventArgs e)
        /// <summary>
        /// This method is called when the _fsWatcher's Renamed event has been fired.
        /// </summary>
        /// <param name="sender">The <see cref="object"/> that fired the event.</param>
        /// <param name="e">The <see cref="RenamedEventArgs"/> of the event.</param>
        void _fsWatcher_Renamed(object sender, RenamedEventArgs e)
        {
            OnRenamed(e);
        }
        #endregion

        #region public virtual void OnRenamed(RenamedEventArgs e)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        public virtual void OnRenamed(RenamedEventArgs e)
        {
            string oldPath = e.OldFullPath;//.Substring(_items.RootDir.Length);
            string path = e.FullPath;//.Substring(_items.RootDir.Length);
            FSWatcherItemType itemType = Directory.Exists(e.FullPath) ? FSWatcherItemType.Directory : FSWatcherItemType.File;

            if (!_items.Add(new FSWatcherItem(itemType, path, oldPath, FSWatcherEventType.Renamed)))
            {
                return;
            }
            SaveToXml();

            RenamedEventHandler handler = Renamed;
            if (handler != null)
            {
                foreach (RenamedEventHandler singleCast in handler.GetInvocationList())
                {
                    ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke;
                    try
                    {
                        if (syncInvoke != null && syncInvoke.InvokeRequired)
                        {
                            syncInvoke.Invoke(singleCast, new object[] { this, e });
                        }
                        else
                        {
                            singleCast(this, e);
                        }
                    }
                    catch
                    { }
                }
            }

        }
        #endregion

        #region void _fsWatcher_Error(object sender, ErrorEventArgs e)
        /// <summary>
        /// This method is called when the _fsWatcher's Error event has been fired.
        /// </summary>
        /// <param name="sender">The <see cref="object"/> that fired the event.</param>
        /// <param name="e">The <see cref="ErrorEventArgs"/> of the event.</param>
        void _fsWatcher_Error(object sender, ErrorEventArgs e)
        {
            OnError(e);
        }
        #endregion

        #region public virtual void OnError(ErrorEventArgs e)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        public virtual void OnError(ErrorEventArgs e)
        {
            ErrorEventHandler handler = Error;
            if (handler != null)
            {
                foreach (ErrorEventHandler singleCast in handler.GetInvocationList())
                {
                    ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke;
                    try
                    {
                        if (syncInvoke != null && syncInvoke.InvokeRequired)
                        {
                            syncInvoke.Invoke(singleCast, new object[] { this, e });
                        }
                        else
                        {
                            singleCast(this, e);
                        }
                    }
                    catch
                    { }
                }
            }
        }
        #endregion

        #region void _fsWatcher_Deleted(object sender, FileSystemEventArgs e)
        /// <summary>
        /// This method is called when the _fsWatcher's Deleted event has been fired.
        /// </summary>
        /// <param name="sender">The <see cref="object"/> that fired the event.</param>
        /// <param name="e">The <see cref="FileSystemEventArgs"/> of the event.</param>
        void _fsWatcher_Deleted(object sender, FileSystemEventArgs e)
        {
            OnDeleted(e);
        }
        #endregion

        #region public virtual void OnDeleted(FileSystemEventArgs e)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        public virtual void OnDeleted(FileSystemEventArgs e)
        {
            string path = e.FullPath;//.Substring(_items.RootDir.Length);
            FSWatcherItemType itemType = Directory.Exists(e.FullPath) ? FSWatcherItemType.Directory : FSWatcherItemType.File;

            if (!_items.Add(new FSWatcherItem(itemType, path, string.Empty, FSWatcherEventType.Deleted)))
            {
                return;
            }
            SaveToXml();

            FileSystemEventHandler handler = Deleted;
            if (handler != null)
            {
                foreach (FileSystemEventHandler singleCast in handler.GetInvocationList())
                {
                    ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke;
                    try
                    {
                        if (syncInvoke != null && syncInvoke.InvokeRequired)
                        {
                            syncInvoke.Invoke(singleCast, new object[] { this, e });
                        }
                        else
                        {
                            singleCast(this, e);
                        }
                    }
                    catch
                    { }
                }
            }

        }
        #endregion

        #region void _fsWatcher_Created(object sender, FileSystemEventArgs e)
        /// <summary>
        /// This method is called when the _fsWatcher's Created event has been fired.
        /// </summary>
        /// <param name="sender">The <see cref="object"/> that fired the event.</param>
        /// <param name="e">The <see cref="FileSystemEventArgs"/> of the event.</param>
        void _fsWatcher_Created(object sender, FileSystemEventArgs e)
        {
            OnCreated(e);
        }
        #endregion

        #region public virtual void OnCreated(FileSystemEventArgs e)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        public virtual void OnCreated(FileSystemEventArgs e)
        {
            string path = e.FullPath;//.Substring(_items.RootDir.Length);
            FSWatcherItemType itemType = Directory.Exists(e.FullPath) ? FSWatcherItemType.Directory : FSWatcherItemType.File;

            if (!_items.Add(new FSWatcherItem(itemType, path, string.Empty, FSWatcherEventType.Created)))
            {
                return;
            }
            SaveToXml();

            FileSystemEventHandler handler = Created;
            if (handler != null)
            {
                foreach (FileSystemEventHandler singleCast in handler.GetInvocationList())
                {
                    ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke;
                    try
                    {
                        if (syncInvoke != null && syncInvoke.InvokeRequired)
                        {
                            syncInvoke.Invoke(singleCast, new object[] { this, e });
                        }
                        else
                        {
                            singleCast(this, e);
                        }
                    }
                    catch
                    { }
                }
            }

        }
        #endregion

        #region void _fsWatcher_Changed(object sender, FileSystemEventArgs e)
        /// <summary>
        /// This method is called when the _fsWatcher's Changed event has been fired.
        /// </summary>
        /// <param name="sender">The <see cref="object"/> that fired the event.</param>
        /// <param name="e">The <see cref="FileSystemEventArgs"/> of the event.</param>
        void _fsWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            OnChanged(this, e);
        }
        #endregion

        #region public virtual void OnChanged(object sender, FileSystemEventArgs e)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public virtual void OnChanged(object sender, FileSystemEventArgs e)
        {
            string path = e.FullPath;//.Substring(_items.RootDir.Length);
            FSWatcherItemType itemType = Directory.Exists(e.FullPath) ? FSWatcherItemType.Directory : FSWatcherItemType.File;
            if (!_items.Add(new FSWatcherItem(itemType, path, string.Empty, FSWatcherEventType.Changed)))
            {
                return;
            }
            SaveToXml();

            FileSystemEventHandler handler = Changed;
            if (handler != null)
            {
                foreach (FileSystemEventHandler singleCast in handler.GetInvocationList())
                {
                    ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke;
                    try
                    {
                        if (syncInvoke != null && syncInvoke.InvokeRequired)
                        {
                            syncInvoke.Invoke(singleCast, new object[] { this, e });
                        }
                        else
                        {
                            singleCast(this, e);
                        }
                    }
                    catch
                    { }
                }
            }
        }
        #endregion

        #region private bool LoadFromXml()
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private bool LoadFromXml()
        {
            try
            {
                string file = System.IO.Path.Combine(_xmlPersistencePath, "items.xml");
                if (File.Exists(file))
                {
                    string stream = string.Empty;
                    using (TextReader rdr = new StreamReader(file))
                    {
                        stream = rdr.ReadToEnd();
                    }
                    FSWatcherItemCollection collection = (FSWatcherItemCollection)XmlSerialization.FromXML(stream, typeof(FSWatcherItemCollection));
                    this.Path = collection.RootDir;//.Substring(0, collection.RootDir.Length - 1);
                    this._items.RootDir = collection.RootDir;
                    this._items.Clear();
                    foreach (FSWatcherItem item in collection.Items)
                    {
                        _items.Add(item);
                    }
                    OnItemHistory(this, new FSWatcherHistoryItemEventArgs(collection.Items));
                }
                else
                {
                    return false;
                }
            }
            catch
            {
                return false;
            }
            return true;
        }
        #endregion

        #region private void SaveToXml()
        /// <summary>
        /// 
        /// </summary>
        private void SaveToXml()
        {
            string xml = XmlSerialization.ToXML(_items);

            using (TextWriter tw = new StreamWriter(System.IO.Path.Combine(_xmlPersistencePath, "items.xml")))
            {
                tw.Write(xml);
                tw.Flush();
            }

        }
        #endregion

        public void OnItemHistory(object sender, FSWatcherHistoryItemEventArgs e)
        {
            FSWatcherHistoryItemHandler handler = ItemHistory;

            //if (handler != null)
            //{
            //    handler(sender, e);
            //}

            if (handler != null)
            {
                foreach (FSWatcherHistoryItemHandler singleCast in handler.GetInvocationList())
                {
                    ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke;
                    try
                    {
                        if (syncInvoke != null && syncInvoke.InvokeRequired)
                        {
                            syncInvoke.Invoke(singleCast, new object[] { this, e });
                        }
                        else
                        {
                            singleCast(this, e);
                        }
                    }
                    catch
                    { }
                }
            }

        }
    }

    public class FSWatcherHistoryItemEventArgs
    {
        private List<FSWatcherItem> _items = new List<FSWatcherItem>();
        public List<FSWatcherItem> Items
        {
            get { return _items; }
        }

        public FSWatcherHistoryItemEventArgs(List<FSWatcherItem> items)
        {
            _items = items;
        }
    }
}
